-
-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better TS #302
Better TS #302
Conversation
I needed better types for `parse-elm`, and ended up with this beauty. Disclaimer: `NestedEntrypoints` type courtesy of Copilot.
You can throw anything. Now, we only have to deal with Errors. Probably a slight perf hit, but likely only on the unhappy path, so it's not as critical.
Uhhhh...... Effect is heavy, and TS didn't like the way message passing was done... And, so I uh... wrote a result type. And then I made it generic, so voilà! The only issue is that TS can't type pipes, so I'd need to make a fluent interface or write a ton of overloads. I opted to just force normal functions.
New and removed dependencies detected. Learn more about Socket for GitHub ↗︎
|
Address review comments from jfmengels#301.
@jfmengels, without more context for the intent of this code, I'm not sure where the floating promises were intentional. I've marked them as ignored so that we don't introduce more issues, but I'd appreciate any insight you can provide on further steps.
@jfmengels, ping pong 🏓 |
* @param {unknown} error | ||
*/ | ||
function intoError(error) { | ||
return error instanceof Error ? error : new Error(String(error)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious, did you notice places where we (potentially) wrapped an exception into multiple Error
s? (nested I mean)
Related: did you notice places where we throw non-Errors? 🤔
(Note, I haven't read the rest of the commits yet)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious, did you notice places where we (potentially) wrapped an exception into multiple
Error
s? (nested I mean)
Nope.
Related: did you notice places where we throw non-Errors? 🤔 (Note, I haven't read the rest of the commits yet)
Yeah, wenode-elm-compiler
throws strings in a few places.
const ElmCommunication = require('./elm-communication'); | ||
const loadCompiledElmApp = require('./load-compiled-app'); | ||
const ResultCache = require('./result-cache'); | ||
|
||
/** | ||
* @type {WorkerThreads<WorkerData>} | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the type annotation not ideally be placed at the definition of workerThreads
? (That at least sounds a lot more intuitive to me)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried that, but it doesn't like it.
@@ -15,9 +16,13 @@ if (parentPort) { | |||
parentPort.on('message', async (url) => { | |||
try { | |||
const response = await getBody(url); | |||
requestPort.postMessage(response); | |||
requestPort.postMessage( | |||
/** @satisfies {PortResponse} */ (result.succeed(response)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make it look even more like Elm!
const Result = require('./result');
Result.succeed(response)
Maybe even call the constructors Ok
and Err
(or Fail
, because I can see it could be confusing with Error/exceptions)
if (flag.alias !== undefined) { | ||
object[flag.name] = flag.alias; | ||
} | ||
const args = /** @type {ParsedArgs} */ ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the difference between
const args = /** @type {ParsedArgs} */ ...
and
/** @type {ParsedArgs} */
const args = ...
Are the two different from TS' point of view?
(Or is this just a styling change?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first one is a cast, the second one is a type annotation. I don't recall why this needed a cast.
@@ -338,7 +340,10 @@ async function makeGitHubApiRequest(options, url, handleNotFound) { | |||
// TODO(@lishaduck) [engine:node@>=21]: We can use `fetch` now. | |||
const response = await got(url, parameters); | |||
|
|||
return response.body; | |||
/** @type {{body: unknown}} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing this over and over, I feel like TS
should try to understand type annotations on a return statement like
/** @type {{body: unknown}} */
return response.body;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's actually a ts-eslint issue: typescript-eslint/typescript-eslint#1682. It can't read JSDoc casts b/c they're technically just comments, so the type queries are wrong. no-unsafe-return
bans returning any
, which is good, but can't read immediate casts, it needs an intermediate variable. In normal TS, you can just use as
.
); | ||
|
||
return /** @type {ApplicationDependencies} */ (dependencies); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just so I understand, the /** @type {unknown} */
is for correctly annotating the value of dependencies
, and the type annotation here is to cast?
Or are they both for casting? In which case, why not cast only once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When using JSON.parse
(& other lib functions), you generally want to cast to unknown, then to another type (the as unknown as
pattern), because they don't default to unknown
for back compat.
I'd guess it's not a direct return b/c of no-unsafe-returns
.
@@ -29,7 +30,8 @@ async function collect(options, elmJson, elmVersion) { | |||
let hasDependenciesThatCouldNotBeDownloaded = false; | |||
|
|||
const projectDepsPromises = Object.entries(dependenciesEntries).map( | |||
async ([name, constraint]) => { | |||
async ([pkgname, constraint]) => { | |||
const name = /** @type {PackageName} */ (pkgname); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Mostly out of curiosity) Could this type potentially be inferred by typing dependenciesEntries
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is typed, but because of excess properties, ts can't infer it: FAQ § Excess Properties Are OK.
@@ -12,15 +12,16 @@ expect.extend({toMatchFile}); | |||
|
|||
/** | |||
* @param {string} args | |||
* @param {Options} [options=undefined] | |||
* @param {Options} [options] | |||
* @returns |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the type not missing here? (same in the rest of the file)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With options, the []
syntax defaults to undefined.
With the @returns
, I must've just missed it.
@@ -139,7 +139,7 @@ async function initializeApp(options, elmModulePath, reviewElmJson, appHash) { | |||
* @returns {never} | |||
*/ | |||
(errors) => { | |||
Report.report(options, {errors}); | |||
void Report.report(options, {errors}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I knew that worker.terminate()
returned a Promise
back when I wrote it, but I think all the floating promises in this commit make sense to keep as floating (for concurrency purposes, etc.).
The only one I'd be worried about it potentially this one, but so far it has always worked well so... I think it's fine to keep as is 🤷
Great work! Btw, thank you for your nice commit etiquette for your PRs. It's easy to review each commit (except maybe for 3965c34 😛 , although it was not that bad in practice) |
Follow up to #301.
Works on TSESlint-based safety.
Probably broke some stuff with error handling.
Please review closely,1 some of these changes might be better served with ignores. Then again, ignores are evil, so... I just fixed them all and hoped for the best.
Oh, and I added more JSDocs for literally everything! Yay!
Footnotes
EDIT: 🤣 ↩